package edu.northwestern.cbits.purple_robot_manager.output;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
import edu.northwestern.cbits.purple_robot_manager.annotation.ScriptingEngineMethod;
import edu.northwestern.cbits.purple_robot_manager.scripting.JavaScriptEngine;
import edu.northwestern.cbits.purple_robot_manager.scripting.SchemeEngine;
public class BootstrapSiteExporter
{
private static final String LANGUAGE_ALL = "All";
private static final String METHOD_NAME = "name";
private static final String METHOD_LANGUAGE = "language";
private static final String METHOD_CATEGORY = "category";
private static final String METHOD_ASSET_PATH = "path";
private static final String METHOD_FULL_NAME = "full_name";
public static final int TEMPLATE_TYPE_BOOTSTRAP = 0;
private static final int TEMPLATE_TYPE_JEKYLL = 1;
public static Uri exportSite(Context context)
{
File exportDirectory = context.getExternalFilesDir(null);
String filename = System.currentTimeMillis() + "_bootstrap.zip";
File exportedFile = new File(exportDirectory, filename);
AssetManager assets = context.getAssets();
try {
FileOutputStream out = new FileOutputStream(exportedFile);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(out));
try
{
BootstrapSiteExporter.writeStaticAssets(assets, zos, "embedded_website/css");
BootstrapSiteExporter.writeStaticAssets(assets, zos, "embedded_website/fonts");
BootstrapSiteExporter.writeStaticAssets(assets, zos, "embedded_website/images");
BootstrapSiteExporter.writeStaticAssets(assets, zos, "embedded_website/js");
BootstrapSiteExporter.writeScriptDocumentation(context, zos, "embedded_website/docs/all", BootstrapSiteExporter.TEMPLATE_TYPE_BOOTSTRAP);
BootstrapSiteExporter.writeScriptDocumentation(context, zos, "embedded_website/docs/scheme", BootstrapSiteExporter.TEMPLATE_TYPE_BOOTSTRAP);
BootstrapSiteExporter.writeScriptDocumentation(context, zos, "embedded_website/docs/javascript", BootstrapSiteExporter.TEMPLATE_TYPE_BOOTSTRAP);
} finally {
zos.close();
}
} catch (java.io.IOException e) {
e.printStackTrace();
}
return Uri.fromFile(exportedFile);
}
private static void writeScriptDocumentation(Context context, ZipOutputStream out, String path, int templateType)
{
String[] components = path.split("/");
String language = components[components.length - 1];
byte[] indexPage = BootstrapSiteExporter.fetchContent(context, language, null, true, templateType);
try {
ZipEntry entry = new ZipEntry(path + "/index.html");
out.putNextEntry(entry);
out.write(indexPage);
out.closeEntry();
JSONArray methods = BootstrapSiteExporter.methodsForLanguage(context, language);
HashSet<String> loaded = new HashSet<>();
for (int i = 0; i < methods.length(); i++)
{
try
{
JSONObject methodDef = methods.getJSONObject(i);
if (methodDef.has(BootstrapSiteExporter.METHOD_ASSET_PATH))
{
String[] methodComponents = methodDef.getString(BootstrapSiteExporter.METHOD_ASSET_PATH).split("/");
String page = methodComponents[methodComponents.length - 1];
if (loaded.contains(page) == false) {
String pageLanguage = language;
if (page.startsWith("all_"))
pageLanguage = "all";
byte[] methodContent = BootstrapSiteExporter.fetchContent(context, pageLanguage, page, true, templateType);
try {
ZipEntry methodEntry = new ZipEntry(path + "/" + page);
out.putNextEntry(methodEntry);
out.write(methodContent);
out.closeEntry();
} catch (ZipException e) {
e.printStackTrace();
}
loaded.add(page);
}
}
}
catch (JSONException e)
{
e.printStackTrace();
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
private static void writeStaticAssets(AssetManager assets, ZipOutputStream out, String path)
{
try
{
for (String item : assets.list(path))
{
try {
InputStream fileIn = assets.open(path + "/" + item);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedInputStream bin = new BufferedInputStream(fileIn);
byte[] buffer = new byte[8192];
int read = 0;
while ((read = bin.read(buffer, 0, buffer.length)) != -1)
{
baos.write(buffer, 0, read);
}
bin.close();
baos.close();
ZipEntry entry = new ZipEntry(path + "/" + item);
out.putNextEntry(entry);
out.write(baos.toByteArray());
out.closeEntry();
}
catch (IOException e)
{
BootstrapSiteExporter.writeStaticAssets(assets, out, path + "/" + item);
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
private static JSONArray methodsForLanguage(Context context, String language)
{
Class[] scriptingClasses = { JavaScriptEngine.class, SchemeEngine.class };
ArrayList<String> languages = new ArrayList<>();
languages.add(BootstrapSiteExporter.LANGUAGE_ALL.toLowerCase());
if (languages.contains(language) == false)
languages.add(language.toLowerCase());
JSONArray declaredMethods = new JSONArray();
HashSet<String> included = new HashSet<>();
for (Class classObj : scriptingClasses)
{
Method[] methods = classObj.getMethods();
for (Method method : methods)
{
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations)
{
if (annotation instanceof ScriptingEngineMethod)
{
ScriptingEngineMethod scriptAnnotation = (ScriptingEngineMethod) annotation;
String category = context.getString(scriptAnnotation.category());
String assetPath = scriptAnnotation.assetPath();
String scriptLanguage = scriptAnnotation.language();
String methodName = method.getName();
if (languages.contains(scriptLanguage.toLowerCase()) && included.contains(method.toGenericString()) == false)
{
StringBuilder args = new StringBuilder();
for (String argument : ((ScriptingEngineMethod) annotation).arguments())
{
if (args.length() > 0)
args.append(", ");
args.append(argument);
}
JSONObject methodDef = new JSONObject();
try {
methodDef.put(BootstrapSiteExporter.METHOD_NAME, methodName + "(" + args.toString() + ")");
methodDef.put(BootstrapSiteExporter.METHOD_FULL_NAME, method.toGenericString());
methodDef.put(BootstrapSiteExporter.METHOD_LANGUAGE, scriptLanguage);
methodDef.put(BootstrapSiteExporter.METHOD_CATEGORY, category);
if (assetPath.trim().length() > 0)
methodDef.put(BootstrapSiteExporter.METHOD_ASSET_PATH, assetPath);
} catch (JSONException e) {
e.printStackTrace();
}
declaredMethods.put(methodDef);
included.add(method.toGenericString());
}
}
}
}
}
return declaredMethods;
}
public static byte[] fetchContent(Context context, String language, String page, boolean readOnly, int templateType)
{
Class[] scriptingClasses = { JavaScriptEngine.class, SchemeEngine.class };
language = language.toLowerCase();
ArrayList<String> languages = new ArrayList<>();
languages.add(BootstrapSiteExporter.LANGUAGE_ALL.toLowerCase());
if (languages.contains(language) == false)
languages.add(language.toLowerCase());
if (page == null || page.trim().length() == 0)
{
try
{
JSONArray declaredMethods = BootstrapSiteExporter.methodsForLanguage(context, language);
AssetManager am = context.getAssets();
String template = "embedded_website/docs/scripting_template.html";
if (readOnly && templateType == BootstrapSiteExporter.TEMPLATE_TYPE_BOOTSTRAP)
template = "embedded_website/docs/scripting_template_readonly.html";
else if (templateType == BootstrapSiteExporter.TEMPLATE_TYPE_JEKYLL)
template = "embedded_website/docs/scripting_template_jekyll.html";
InputStream in = am.open(template);
// http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
Scanner s = new Scanner(in).useDelimiter("\\A");
String content = "";
if (s.hasNext())
content = s.next();
content = content.replace("{{ METHOD_DEFINITIONS }}", declaredMethods.toString(2));
content = content.replace("{{ LANGUAGE }}", language.toLowerCase());
return content.getBytes(Charset.forName("UTF-8"));
}
catch (IOException | JSONException e)
{
e.printStackTrace();
}
return "404 ERROR".getBytes(Charset.forName("UTF-8"));
}
else
{
if (page.startsWith("all_"))
language = "all";
try
{
AssetManager am = context.getAssets();
String template = "embedded_website/docs/method_template.html";
if (readOnly && templateType == BootstrapSiteExporter.TEMPLATE_TYPE_BOOTSTRAP)
template = "embedded_website/docs/method_template_readonly.html";
else if (templateType == BootstrapSiteExporter.TEMPLATE_TYPE_JEKYLL)
template = "embedded_website/docs/method_template_jekyll.html";
InputStream in = am.open(template);
// http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
Scanner s = new Scanner(in).useDelimiter("\\A");
String content = "";
if (s.hasNext())
content = s.next();
s.close();
in = am.open("embedded_website/docs/" + language.toLowerCase() + "/" + page);
s = new Scanner(in).useDelimiter("\\A");
String pageContent = "";
if (s.hasNext())
pageContent = s.next();
s.close();
for (Class classObj : scriptingClasses)
{
Method[] methods = classObj.getMethods();
for (Method method : methods)
{
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations)
{
if (annotation instanceof ScriptingEngineMethod)
{
ScriptingEngineMethod scriptAnnotation = (ScriptingEngineMethod) annotation;
if (page.equals(scriptAnnotation.assetPath()))
{
StringBuilder args = new StringBuilder();
for (String argument : ((ScriptingEngineMethod) annotation).arguments())
{
if (args.length() > 0)
args.append(", ");
args.append(argument);
}
content = content.replace("{{ METHOD_NAME }}", method.getName() + "(" + args.toString() + ")");
content = content.replace("{{ LANGUAGE }}", ((ScriptingEngineMethod) annotation).language().toLowerCase());
content = content.replace("{{ PAGE }}", page);
}
}
}
}
}
content = content.replace("{{ METHOD_DOCUMENTATION }}", pageContent);
return content.getBytes(Charset.forName("UTF-8"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
return "404 ERROR".getBytes(Charset.forName("UTF-8"));
}
public static Uri exportJekyllPages(Context context) {
File exportDirectory = context.getExternalFilesDir(null);
String filename = System.currentTimeMillis() + "_jekyll.zip";
File exportedFile = new File(exportDirectory, filename);
try {
FileOutputStream out = new FileOutputStream(exportedFile);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(out));
try
{
BootstrapSiteExporter.writeScriptDocumentation(context, zos, "embedded_website/docs/all", BootstrapSiteExporter.TEMPLATE_TYPE_JEKYLL);
BootstrapSiteExporter.writeScriptDocumentation(context, zos, "embedded_website/docs/scheme", BootstrapSiteExporter.TEMPLATE_TYPE_JEKYLL);
BootstrapSiteExporter.writeScriptDocumentation(context, zos, "embedded_website/docs/javascript", BootstrapSiteExporter.TEMPLATE_TYPE_JEKYLL);
} finally {
zos.close();
}
} catch (java.io.IOException e) {
e.printStackTrace();
}
return Uri.fromFile(exportedFile);
}
}